/*
 * Copyright 2013 Agustina Martinez-Garcia

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package uk.ljmu.qudexapps.xmllib.services;

import java.io.File;
import java.io.IOException;
import java.io.InputStream; //import java.net.FileNameMap;
import java.net.URL;
import java.net.URLConnection;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;

import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil;
import fedora.fedoraSystemDef.foxml.DatastreamType;

import uk.ljmu.metadata.schemas.SchemaInstance;
import uk.ljmu.metadata.schemas.SchemaInstanceContainer;
import uk.ljmu.metadata.schemasutilities.SchemasProcessing;
import uk.ljmu.metadata.utilities.MetadataUtilities;
import uk.ljmu.metadata.utilities.MetadataUtilities.ValueType;
import uk.ljmu.qudexapps.xmllib.exceptions.XLSException;
import uk.ljmu.qudexapps.xmllib.model.ColumnMap;
import uk.ljmu.qudexapps.xmllib.model.SpreadsheetMap;
import uk.ljmu.qudexapps.xmllib.utilities.Constants;
import uk.ljmu.qudexapps.xmllib.utilities.SpreadsheetUtilities.Column;

/**
 * Implements the management interface for EXCEL spreadsheets
 * 
 * @author agustinamartinez
 * 
 */
public class ExcelManagementImpl implements SpreadsheetManagement {
    private static Logger log = Logger.getLogger(ExcelManagementImpl.class);
    /**
     * boolean specifying the type of spreadsheet: normal ss or qudex ss
     */
    private boolean qudexSS = false;
    
    /**
     * Default ID header - must be present in the spreadsheet
     */
    private static final String PID_HEADER = "pid";
    /**
     * Compatibility with Exhibit - LABEL header must be present
     */
    private static final String LABEL_HEADER = "label";

    /**
     * Map structure to store the spreadsheet contents
     */
    private SpreadsheetMap mappingReader;
    /**
     * SchemaInstance Container structure to hold the contained metadata
     * elements
     */
    private SchemaInstanceContainer siContainer;
    private boolean qudexCollection;

    /**
     * @param qudexSS
     *            the qudexSS to set
     */
    public void setQudexSS(boolean qudexSS) {
	this.qudexSS = qudexSS;
    }

    /**
     * @return the qudexSS
     */
    public boolean isQudexSS() {
	return qudexSS;
    }

    /**
     * @param siContainer
     *            the siContainer to set
     */
    public void setSiContainer(SchemaInstanceContainer siContainer) {
	this.siContainer = siContainer;
    }

    /**
     * @return the siContainer
     */
    public SchemaInstanceContainer getSiContainer() {
	return siContainer;
    }

    /**
     * Set the map structure
     * 
     * @return the mappingReader
     */
    public SpreadsheetMap getMappingReader() {
	return mappingReader;
    }

    /**
     * Get value of map structure
     * 
     * @param mappingReader
     *            the mappingReader to set
     */
    public void setMappingReader(SpreadsheetMap mappingReader) {
	this.mappingReader = mappingReader;
    }

    /**
     * Constructor - initialises the map structure and the fields coming from
     * the configuration file
     */
    public ExcelManagementImpl() {

	mappingReader = new SpreadsheetMap();
	siContainer = new SchemaInstanceContainer();
	this.setQudexSS(false);
    }

    /**
     * Constructor - initialises the map structure and the fields coming from
     * the configuration file
     */
    public ExcelManagementImpl(boolean isQudex, boolean isQudexCollection) {

	mappingReader = new SpreadsheetMap();
	siContainer = new SchemaInstanceContainer();

	this.qudexSS = isQudex;
	this.qudexCollection = isQudexCollection;
    }

    // METHODS
    /**
     * Reads an Excel spreadsheet containing QuDeX, DDI and Fedora datastreams
     * information to ingest into Fedora
     * 
     * @param inputStream
     *            spreadsheet contents
     * @throws XLSException
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public boolean readSpreadsheet(InputStream inputStream)
    throws XLSException, IOException {

	POIFSFileSystem fs;
	HSSFWorkbook wb;

	try {
	    if (inputStream == null) {
		log.fatal("Null spreadsheet");
		throw new XLSException("Null Spreadsheet...");
	    }

	    try {
		fs = new POIFSFileSystem(inputStream);
		wb = new HSSFWorkbook(fs);
	    } catch (IOException e) {
		log.fatal("Unable to read spreadsheet: " + e.getMessage());
		throw new XLSException(
		"Spreadsheet contains invalid formatting. Check formatting.");
	    } catch (OfficeXmlFileException ex) {
		log.fatal("Unable to read spreadsheet: " + ex.getMessage());
		throw new XLSException("Spreadsheet in wrong format: XLSX...");
	    }

	    HSSFSheet sheet = wb.getSheetAt(0);

	    int firstRow = sheet.getFirstRowNum();
	    int lastRow = sheet.getLastRowNum();

	    // Check if empty spreadsheet
	    if (lastRow == 0) {
		if (sheet.getPhysicalNumberOfRows() == 0)
		    throw new XLSException("The uploaded spreadsheet is empty");
	    }

	    // Define column indexes
	    List<Column> columns = new ArrayList<Column>();
	    int r = firstRow;
	    int idColumn = -1;
	    log.info("Parsing header columns...");

	    // AMG- New SchemaInstance Parser under testing
	    HSSFRow headersRow = sheet.getRow(r);
	    int fCell = headersRow.getFirstCellNum();
	    int lCell = headersRow.getLastCellNum();
	    List<String> headersArray = new ArrayList<String>();
	    for (int i = fCell; i < lCell; i++) {
		// AMG - SKIP DATASTREAM HEADERS SINCE PARSING METADATA
		String header = headersRow.getCell(i).toString().trim();
		if (this.isDatastreamHeader(header))
		    continue;
		headersArray.add(header);
	    }
	    int skippedElements = 0;
	    // AMG- TEST NEW SCHEMA PARSER
	    boolean headersParse = siContainer
	    .initialiseSchemasInstanceContainer(headersArray);
	    if (!headersParse) {
		log.fatal("Error while parsing metadata schema headers...");
		throw new XLSException(
		"Error while parsing metadata schema headers...");
	    }
	    try {
		// AMG- Find the header row
		for (; r <= lastRow; r++) {
		    HSSFRow row = sheet.getRow(r);
		    if (row == null) {
			continue;
		    }

		    int firstCell = row.getFirstCellNum();
		    int lastCell = row.getLastCellNum();

		    if (firstCell >= 0 && firstCell <= lastCell) {
			// AMG- Iterate through the cells within the row
			for (int c = 0; c <= lastCell; c++) {
			    HSSFCell cell = row.getCell(c);
			    Column column = null;
			    if (cell != null) {
				String headerName = cell.getStringCellValue()
				.trim();
				if (headerName.length() > 0) {
				    column = new Column();
				    column.setColumnIndex(Integer.valueOf(c));
				    int colon = headerName
				    .indexOf(Constants.PID_DELIMITER);
				    if (colon < 0) {
					column.setColumnName(headerName); // AMG-
					// NO
					// PREFIX
				    } else {
					column.setColumnName(headerName
						.substring(0, colon).trim());
					// AMG- Check if it's a known metadata
					// schema
					boolean schemaAdded = this
					.findSchema(headerName);
					if (schemaAdded) {
					    // AMG- Column name without the
					    // prefix
					    column
					    .setColumnName(headerName
						    .substring(
							    colon + 1,
							    headerName
							    .length()));
					    String prefix = headerName
					    .substring(0, colon);
					    column
					    .setColumnType(MetadataUtilities.MDSchemas
						    .get(prefix));
					} else {
					    // Normal header but with TYPE
					    // specifier
					    String columnType = headerName
					    .substring(colon + 1);
					    ValueType vType = this
					    .getColumTypeFromPrefix(columnType);
					    // AMG- Column not found: IGNORE
					    if (vType == null)
						continue;
					    column.setColumnName(headerName
						    .substring(0, colon));
					    column.setColumnType(vType);
					}

				    }
				    // AMG - For non Qudex ss's look for the
				    // Unique mandatory field
				    if (column.getColumnName()
					    .equalsIgnoreCase(PID_HEADER)
					    || column.getColumnName()
					    .equalsIgnoreCase(
						    LABEL_HEADER)) {
					if (qudexSS
						&& column.getColumnName()
						.equalsIgnoreCase(
							PID_HEADER) 
						&& !qudexCollection) {
					    skippedElements++;
					    continue;
					}
					if (column.getColumnName()
						.equalsIgnoreCase(PID_HEADER))
					    idColumn = c;
				    }
				}
			    }
			    if (column != null)
				columns.add(column);
			} // AMG- END iterating through row
			r++;
			// AMG- Just one iteration once found the Headers row
			break;
		    }
		}
	    } catch (Exception ex) {
		log.fatal(ex.getMessage());
		throw new XLSException(
		"Errors occurred while parsing the HEADERS of the spreadsheet.");
	    }
	    // AMG NEW CLEAN INSTANCES WITH NO ELEMENTS
	    siContainer.cleanEmptySchemaInstances();
	    // AMG- Check if we've found all the mandatory headers
	    mappingReader.setGlobalMap(new LinkedHashMap<String, ColumnMap>());

	    // AMG- Check only for non qudex ss
	    if (!qudexSS) {
		if (idColumn < 0) {
		    log.fatal("ID or LABEL column not found...");
		    throw new XLSException(
		    "ID or LABEL header not found in the spreadsheet. This header is mandatory.");
		}
	    }

	    log.info("Parsing spreadsheet rows...");
	    try {

		// The first pass will collect all the items and their
		// properties
		for (; r <= lastRow; r++) {
		    HSSFRow row = sheet.getRow(r);
		    if (row == null) {
			continue;
		    }

		    int firstCell = row.getFirstCellNum();
		    //int lastCell = row.getLastCellNum() - skippedElements;
		    int lastCell = columns.size() - 1 - skippedElements;
		    if (firstCell >= 0 && firstCell <= lastCell) {
			HSSFCell cell;
			ColumnMap item = null;
			//TODO test The correct management with new support for qudexCollection PIDs
			if (!qudexSS || qudexCollection) {
			    if (qudexCollection && (idColumn < 0)) {
				 // AMG- for qudex ss we generate a unique identifier
				    // for each row since PID column is not present
				    UUID uuid = UUID.randomUUID();
				    item = new ColumnMap(HSSFCell.class.getName());
				    item.setIdentifier(uuid.toString());
				    mappingReader.getGlobalMap().put(uuid.toString(),
					    item);
			    } else {
				cell = row.getCell(idColumn);
				if (cell == null) {
				    if (qudexCollection) {
					UUID uuid = UUID.randomUUID();
					cell = row.createCell(idColumn);
					cell.setCellValue(uuid.toString());
				    } else continue;
				}

				//String id = getCellString(cell);
				//if (id == null || id.length() == 0) {
				//    continue;
				//}

				//item = mappingReader.getGlobalMap().get(id);
				//if (item == null) {
				item = new ColumnMap(HSSFCell.class.getName());
				item.setIdentifier(getCellString(cell));
				mappingReader.getGlobalMap().put(getCellString(cell), item);
				//}
			    }
			} else {
			    // AMG- for qudex ss we generate a unique identifier
			    // for each row since PID column is not present
			    UUID uuid = UUID.randomUUID();
			    item = new ColumnMap(HSSFCell.class.getName());
			    item.setIdentifier(uuid.toString());
			    mappingReader.getGlobalMap().put(uuid.toString(),
				    item);
			}
			for (int c = 0; c <= lastCell; c++) {
			    
			    if (columns.get(c) != null) {
        			    Column column = columns.get(c);

				if (column != null) {
				    cell = row.getCell(column.getColumnIndex());
				    if (cell != null) {
					int cellType = cell.getCellType();
					if (cellType == HSSFCell.CELL_TYPE_BLANK
						|| cellType == HSSFCell.CELL_TYPE_ERROR) {
					    continue;
					}
					((Map<Column, HSSFCell>) item
						.getColumnMap()).put(column,
						(HSSFCell) cell);
				    }

				}
			    }
			}
		    }
		}
	    } catch (Exception ex) {
		log.fatal(ex.getMessage());
		throw new XLSException(
		"Errors occurred while parsing CONTENTS of the spreadsheet. Check error log for more detailed information.");
	    }
	    log.info("Finished parsing spreadsheet...");
	    return true;
	} finally {
	    if (inputStream != null)
		inputStream.close();
	}
    }

    /**
     * Return the Hash map containing the spreadsheet parsing
     */
    public SpreadsheetMap getSpreadsheetMap() {
	return this.mappingReader;
    }

    // UTILITIES FOR PARSING SPECIFIC PARTS: DC SCHEMA, RELS_-EXT DS's, OTHERS
    /**
     * For a given object returns the XML string of the DC or RELS-EXT elements,
     * if present for that object
     * 
     * @param utils
     * @param identifier
     *            key identifier for the item in the map
     * @param typeValidation
     *            ValueType item specifying the schema to validate
     * @return string containing the XML DC document, otherwise ""
     */
    public String constructDocument(String identifier,
	    MetadataUtilities.ValueType typeValidation, String objectPID) {
	boolean hasElements = false;

	hasElements = this.hasFields(this.getSpreadsheetMap(), typeValidation);

	if (hasElements) {
	    String results = "";

	    if (typeValidation.equals(MetadataUtilities.ValueType.DcElement)) {
		results = constructDublincoreTemplate(identifier);
	    } else if (typeValidation
		    .equals(MetadataUtilities.ValueType.RELS_EXT)) {
		results = constructFedoraRelationshipsTemplate(identifier,
			objectPID, true);
	    }

	    return results;
	}
	// No DC elements or Fedora REL elements
	return "";
    }

    /**
     * 
     * @param utils
     * @param identifier
     * @return XML string containing the DC datastream contents
     */
    public String constructDublincoreTemplate(String identifier) {
	// Create document
	Element root = DocumentHelper.createElement("oai_dc:dc");
	root.addNamespace("oai_dc", MetadataUtilities.RELS_MAP.get("oai_dc"));
	root.addNamespace("dc", MetadataUtilities.RELS_MAP.get("dc"));
	Document doc = DocumentHelper.createDocument(root);

	ColumnMap item;

	item = this.getSpreadsheetMap().getGlobalMap().get(identifier);
	// Caught Null pointer exception
	if (item == null)
	    return "";
	for (Column column : item.getColumnMap().keySet()) {
	    if (column.getColumnType().equals(
		    MetadataUtilities.ValueType.DcElement)) {
		Element element = DocumentHelper.createElement(column
			.getColumnName());
		HSSFCell cell = (HSSFCell) item.getColumnMap().get(column);
		element.setText(this.returnStringCellContent((HSSFCell) cell));

		root.add(element);
	    }
	}
	return doc.asXML();
    }

    /**
     * 
     * @param identifier
     * @param elementsPresent
     * @return XML string containing the RELS-EXT datastream contents
     */
    private String constructFedoraRelationshipsTemplate(String identifier,
	    String objectPID, boolean elementsPresent) {
	// Create document
	Element root = DocumentHelper.createElement("rdf:RDF");
	root.addNamespace("rdf", MetadataUtilities.RELS_MAP.get("rdf"));
	root.addNamespace("fedora-model", MetadataUtilities.RELS_MAP
		.get("fedora-model"));
	root.addNamespace("rel", MetadataUtilities.RELS_MAP.get("rel"));
	Element description = DocumentHelper.createElement("rdf:Description");
	description.addAttribute("rdf:about",
		!objectPID.startsWith("info") ? MetadataUtilities.OBJECTID
			+ objectPID : objectPID);
	root.add(description);
	Document doc = DocumentHelper.createDocument(root);

	if (elementsPresent) {
	    ColumnMap item;
	    item = this.getSpreadsheetMap().getGlobalMap().get(identifier);
	    String str = "";
	    for (Column column : item.getColumnMap().keySet()) {
		// AMG - Now has to manage also rdf:type element which indicate
		// whether the object is a
		// collection or NOT - Not using dc:type anymore
		if (column.getColumnType().equals(
			MetadataUtilities.ValueType.RELS_EXT)
			|| (column.getColumnType().equals(
				MetadataUtilities.ValueType.RdfElement) && column
				.getColumnName().equalsIgnoreCase("type"))) {
		    String qualifiedName = "";

		    if (column.getColumnType().equals(
			    MetadataUtilities.ValueType.RELS_EXT))
			qualifiedName = "rel:" + column.getColumnName();
		    else
			qualifiedName = column.getColumnName();

		    HSSFCell cell = (HSSFCell) item.getColumnMap().get(column);
		    Element elem = DocumentHelper.createElement(qualifiedName);

		    // Check if we have a list of values
		    String cellValue = this
		    .returnStringCellContent((HSSFCell) cell);
		    // TODO Check all the split(';') and add the trim() call
		    String[] listValues = cellValue.trim().split(
			    Constants.FIELDS_SEPARATOR);

		    if (listValues.length > 1) {
			for (String strValue : listValues) {
			    if (column.getColumnType().equals(
				    MetadataUtilities.ValueType.RELS_EXT)) {
				if (!strValue
					.startsWith(MetadataUtilities.OBJECTID))
				    str = MetadataUtilities.OBJECTID + strValue;
				elem.addAttribute("rdf:resource",
					(str != "") ? str : strValue);
				str = "";
				description.add(elem);
				elem = DocumentHelper
				.createElement(qualifiedName);
			    } else {
				// AMG - Manage rdf:type collection element
				if (column.getColumnType().equals(
					MetadataUtilities.ValueType.RdfElement)
					&& column.getColumnName()
					.equalsIgnoreCase("type")) {
				    // AMG - MANAGE THIS
				    if (!SchemasProcessing
					    .checkValidURI(strValue)) {
					if (SchemasProcessing
						.isPartOfDCMI(strValue)) {
					    if (!cellValue.startsWith("http"))
						str = MetadataUtilities.MDSchemasUrl
						.get("dcmitype")
						+ strValue;
					} else {
					    // AMG - We skip it, bad formed
					    // element
					    continue;
					}
				    }
				    elem.setQName(new QName("rdf:"
					    + column.getColumnName()));
				    elem.addAttribute("rdf:resource",
					    (str != "") ? str : strValue);
				    str = "";
				    description.add(elem);
				    elem = DocumentHelper.createElement(column
					    .getColumnName());
				}
			    }
			}
		    } else {
			if (column.getColumnType().equals(
				MetadataUtilities.ValueType.RELS_EXT)) {
			    if (!cellValue
				    .startsWith(MetadataUtilities.OBJECTID))
				str = MetadataUtilities.OBJECTID + cellValue;
			    elem.addAttribute("rdf:resource", (str != "") ? str
				    : cellValue);
			    str = "";
			    description.add(elem);
			    elem = DocumentHelper.createElement(qualifiedName);
			} else {
			    // AMG - Manage rdf:type collection element
			    if (column.getColumnType().equals(
				    MetadataUtilities.ValueType.RdfElement)
				    && column.getColumnName().equalsIgnoreCase(
				    "type")) {
				// TODO - MANAGE THIS
				if (!SchemasProcessing.checkValidURI(cellValue)) {
				    if (SchemasProcessing
					    .isPartOfDCMI(cellValue)) {
					if (!cellValue.startsWith("http"))
					    str = MetadataUtilities.MDSchemasUrl
					    .get("dcmitype")
					    + cellValue;
				    } else {
					// AMG - We skip it, bad formed element
					continue;
				    }
				}
				elem.setQName(new QName("rdf:"
					+ column.getColumnName()));
				elem.addAttribute("rdf:resource",
					(str != "") ? str : cellValue);
				str = "";
				description.add(elem);
				elem = DocumentHelper.createElement(column
					.getColumnName());
			    }
			}
		    }

		}
	    }
	}

	return doc.asXML();
    }

    /**
     * Iterates to the map of elements from the datastreams spreadsheet to find
     * any column storing DC values, RELS-EXT values, other schemas
     * 
     * @return true if found DC element, otherwise false
     */
    public boolean hasFields(SpreadsheetMap mappingReader,
	    MetadataUtilities.ValueType value) {
	for (ColumnMap item : mappingReader.getGlobalMap().values()) {
	    for (Column column : item.getColumnMap().keySet()) {
		if (column.getColumnType().equals(value)
			|| (value.equals(MetadataUtilities.ValueType.RELS_EXT) && column
				.getColumnType().equals(
					MetadataUtilities.ValueType.RdfElement)))
		    return true;
	    }
	}
	return false;
    }

    /**
     * Obtains the mimetype of a file either by its path or URL
     * 
     * @param type
     *            1 for file path, for URL other
     * @param path
     *            file path or file URL
     * @return string mimetype of the input file
     * @throws IOException
     */
    public String getMimeType(int type, String path) throws IOException {

	if (type == 1) {
	    // FILE
	    File f = new File(path);
	    MimeUtil
	    .registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
	    Collection<?> mimeTypes = MimeUtil.getMimeTypes(f);
	    if (!mimeTypes.isEmpty()) {
		MimeType mtype = (MimeType) mimeTypes.iterator().next();
		return mtype.toString();
	    } else
		return "";
	} else {
	    URL u = new URL(path);
	    URLConnection uc = null;
	    uc = u.openConnection();
	    String typeFile = uc.getContentType();
	    return typeFile;
	}
    }

    private boolean isDatastreamHeader(String header) {
	String[] splitHeader = header.split(Constants.PID_DELIMITER);

	if (splitHeader.length < 2) {
	    return false;
	}
	// AMG- (15/11) X: inline, M: managed, R: redirected, E: external
	if (splitHeader[1].equalsIgnoreCase(DatastreamType.CONTROLGROUP.X
		.toString())
		|| splitHeader[1]
		               .equalsIgnoreCase(DatastreamType.CONTROLGROUP.M
		        	       .toString())
		        	       || splitHeader[1]
		        	                      .equalsIgnoreCase(DatastreamType.CONTROLGROUP.E
		        	                	      .toString())
		        	                	      || splitHeader[1]
		        	                	                     .equalsIgnoreCase(DatastreamType.CONTROLGROUP.R
		        	                	                	     .toString())
		        	                	                	     || splitHeader[1].equalsIgnoreCase(PID_HEADER))
	    return true;
	else
	    return false;
    }

    // HSSF Excel spreadsheet utilities
    /**
     * Parse Excel cells are return the String equivalent of the text of the
     * cell
     * 
     * @param cell
     *            HSSF cell object
     * @return String with the value of the cell content
     */
    public String returnStringCellContent(Object cell) {

	// AMG- Check if cell is HSSFCell type, if not return NULL
	if (!HSSFCell.class.isInstance(cell)) {
	    log.fatal("returnStringCellContent: cell is not HSSF cell type");
	    return null;
	}
	HSSFCell auxCell = (HSSFCell) cell;
	int cellType = auxCell.getCellType();

	if (cellType == HSSFCell.CELL_TYPE_NUMERIC) {
	    if (HSSFDateUtil.isCellDateFormatted(auxCell)) {
		try {
		    Format formatter;
		    // The year
		    formatter = new SimpleDateFormat(Constants.SS_DATE_FORMAT_ISO); // 02
		    Date d = auxCell.getDateCellValue();
		    return formatter.format(d);
		} catch (Exception e) {
		    log.fatal(e.getMessage());
		    // TODO - Check occurrences of returnStringCellContent since
		    // the return value has changed
		    return null;
		}
	    }

	    try {
		double d = auxCell.getNumericCellValue();
		boolean isDouble = (Double.compare(d, Math.floor(d)) != 0);
		Object object = isDouble ? ((Object) Double.valueOf(d))
			: ((Object) Long.valueOf((long) d));

		return (object instanceof Double) ? String
			.valueOf((Double) object) : String
			.valueOf((Long) object);
	    } catch (Exception e) {
		log.fatal(e.getMessage());
		return null;
	    }
	} else if (cellType == HSSFCell.CELL_TYPE_BOOLEAN) {
	    return String.valueOf(auxCell.getBooleanCellValue());
	}

	return auxCell.getRichStringCellValue().getString();
    }

    public SchemaInstanceContainer getSchemaInstanceContainer() {
	return this.siContainer;
    }

    public String getCellString(Object cell) {
	/*
	 * TODO Check all ocurrences of of getCellString since I've changed the
	 * return value
	 */
	if (!HSSFCell.class.isInstance(cell)) {
	    log.fatal("getCellString: cell is not HSSF cell type");
	    return null;
	}
	HSSFCell auxCell = (HSSFCell) cell;
	int cellType = auxCell.getCellType();
	if (cellType == HSSFCell.CELL_TYPE_BLANK
		|| cellType == HSSFCell.CELL_TYPE_ERROR) {
	    return null;
	}

	if (cellType == HSSFCell.CELL_TYPE_STRING) {
	    return cell.toString().trim();
	}
	if (cellType == HSSFCell.CELL_TYPE_NUMERIC) {
	    try {
		double d = auxCell.getNumericCellValue();
		if (Double.compare(d, Math.floor(d)) != 0) {
		    return Double.toString(d);
		} else {
		    return Long.toString(Math.round(d));
		}
	    } catch (Exception e) {
		log.info(e.getMessage());
	    }
	}

	try {
	    SimpleDateFormat df = new SimpleDateFormat(Constants.SS_DATE_FORMAT_ISO);
	    return df.format(auxCell.getDateCellValue());
	} catch (Exception e) {
	    log.info(e.getMessage());
	}
	if (cellType == HSSFCell.CELL_TYPE_BOOLEAN)
	    return Boolean.toString(auxCell.getBooleanCellValue());
	// AMG- No matching format
	return null;
    }
    /**
     * @param rowItem structure containing all the column values for a given spreadsheet row
     */
    public String getCellContent(Object rowItem, Object columnItem) {
	String value = "";
	try {
	    HSSFCell cell = (HSSFCell) ((ColumnMap) rowItem).getColumnMap().get((Column) columnItem);
	    if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC)
		value = Double.toString(cell.getNumericCellValue());
	    else if (cell.getCellType() == HSSFCell.CELL_TYPE_BOOLEAN) {
		value = Boolean.toString(cell.getBooleanCellValue());
	    } else
		value = cell.getStringCellValue();
	} catch (Exception ex) {
	    log.fatal(ex.getMessage());
	}
	return value;
    }

    /**
     * Clean all the maps when done with the interface intance
     */
    public void cleanInstance() {
	this.siContainer.getListSchemaInstances().clear();
	this.mappingReader.getGlobalMap().clear();
    }

    /**
     * 
     * @return
     */
    public Object getPidFromSpreadsheet(Object cmap) {
	String pid = "";

	for (Column col : ((ColumnMap) cmap).getColumnMap().keySet()) {
	    if (col.getColumnName().equalsIgnoreCase(PID_HEADER)) {
		pid = returnStringCellContent((HSSFCell) ((ColumnMap) cmap).getColumnMap()
			.get(col));
		break;
	    }
	}
	return pid;
    }
    /**
     * @param rowIdentifier
     * @param columnIdentifier
     */
    public Object getColumnByIdentifier(Object rowIdentifier, Object columnIdentifier) {
	
	if (this.mappingReader.getGlobalMap().containsKey(rowIdentifier)) {
	    ColumnMap colMap = this.mappingReader.getGlobalMap().get(rowIdentifier);
	    for (Column column : colMap.getColumnMap().keySet()) {
		    if (column.getColumnName().equalsIgnoreCase((String) columnIdentifier)) {
			return column;
		    }
		}
	}
	//NOT found
	log.info("Column with id: " + columnIdentifier + " not found in Spreadsheet.");
	return null;
    }
    /**
     * Returns the Column Map for a given spreadsheet row
     * 
     * @param rowIdentifer row identifier string to look for
     * 
     * @return Column Map for the given row, if found, null otherwise
     */
    public Object getColumnMap(Object rowIdentifier) {
	if (this.mappingReader.getGlobalMap().containsKey(rowIdentifier)) {
	    return this.mappingReader.getGlobalMap().get(rowIdentifier).getColumnMap();
	}
	//Not found
	log.info("Row wit identifier: " + rowIdentifier + " not found");
	return null;
    }
    
    //PRIVATE METHODS
    /**
     * 
     */
    /*
     * public OutputStream createExcelFile() { // create a new file OutputStream
     * out = null; try { out = new FileOutputStream(new
     * File("/Users/agustinamartinez/Desktop/myfile.xls")); } catch (Exception
     * e1) { log.fatal(e1.getMessage()); } // create a new workbook HSSFWorkbook
     * wb = new HSSFWorkbook(); // create a new sheet HSSFSheet s =
     * wb.createSheet(); HSSFRow r = null;
     * 
     * int rownum1 = 0; int columnIndex = 0;
     * 
     * // create a row r = s.createRow(rownum1); //Create headers for (ColumnMap
     * item : this.getSpreadsheetMap().getGlobalMap().values()) { for () {
     * 
     * } break; } rownum1++; // write the workbook to the output stream // close
     * our file (don't blow out our file handles try { wb.write(out);
     * out.close(); } catch (IOException e) { log.fatal(e.getMessage()); } }
     */
    // PARSING PRIVATE UTILITIES
    @SuppressWarnings("unused")
    private String findSchemaPrefix(String prefix) {
	String str = "";

	ValueType valueType = MetadataUtilities.MDSchemas.get(prefix);

	if (valueType != null) {
	    str = valueType.toString();
	    return str;
	} else
	    return "";
    }
/**
 * Returns true if the element belongs to any of the handled metadata schemas
 * 
 * @param elementName
 * @return true in the element is found in any of the valid metadata schemas handled
 * by the application
 * @throws XLSException if any errors when parsing schemas
 */
    private boolean findSchema(String elementName) throws XLSException {
	boolean enc = false;
	// AMG- EXTRACT PREFIX
	int index = elementName.indexOf(Constants.PID_DELIMITER);
	if (index < 0) {
	    log.fatal("Schema element name not well formed, prefix not set...");
	    throw new XLSException("Schema element name not well formed");
	}
	String prefix = elementName.substring(0, index);
	String name = elementName.substring(index + 1, elementName.length());

	// AMG- NEW SCHEMA PARSER
	for (SchemaInstance schemaIns : siContainer.getListSchemaInstances()) {

	    if (schemaIns.getSchemaPrefix().equalsIgnoreCase(prefix)) {
		// NEW SCHEMA PARSER
		enc = siContainer.addMetadataElementToSchemaInstance(prefix,
			schemaIns.getSchemaURI(), name);
		break;
	    }
	}
	return enc;
    }

    /**
     * Returns the valuetype for a given column prefix, e.g. rels will return 
     * ValueType.RELS_EXT
     * @param columnPrefix string representing the column prefix
     * 
     * @return column valueType
     */
    private ValueType getColumTypeFromPrefix(String columnPrefix) {
	if (columnPrefix
		.equalsIgnoreCase(MetadataUtilities.FEDORA_RELATIONS_NS_PREFIX))
	    return ValueType.RELS_EXT;
	else if (columnPrefix.equalsIgnoreCase(ValueType.Number.toString()))
	    return ValueType.Number;
	else if (columnPrefix.equalsIgnoreCase(ValueType.Boolean.toString()))
	    return ValueType.Boolean;
	else if (columnPrefix.equalsIgnoreCase(ValueType.URL.toString()))
	    return ValueType.URL;
	else if (columnPrefix.equalsIgnoreCase(ValueType.X.toString())) {
	    return ValueType.X;
	} else if (columnPrefix.equalsIgnoreCase(ValueType.E.toString())) {
	    return ValueType.E;
	} else if (columnPrefix.equalsIgnoreCase(ValueType.M.toString())) {
	    return ValueType.M;
	} else if (columnPrefix.equalsIgnoreCase(ValueType.R.toString())) {
	    return ValueType.R;
	}
	log
	    .info("getColumTypeFromPrefix, parsing spreadsheet no valuetype matching...: "
		    + columnPrefix);
	return null;
    }

    public Object getRowColumnValue(Object rowIdentifier, Object columnId) {
	Column col = (Column) this.getColumnByIdentifier(rowIdentifier, columnId);
	Map<Column, ?> map = this.mappingReader.globalMap.get(rowIdentifier).getColumnMap();
	if (col != null && map != null)
	    return this.returnStringCellContent(map.get(col));
	else return null;
    }
}
